// XMLW_xmlwriter.h
//
// Kudos for such a slick implementation of XML output goes to Oboltus for class XmlStream at 
//  http://www.codeproject.com/KB/stl/simple_xmlwriter.aspx
// Also implemented the beautifier update generously donated by codeproject member alan.witmer
/**
  I have implemented a simple class, which behaves as usual std::stream, but also
  track all the needed XML markings. The class implementation is very simple and
  template-based. One may write to XmlStream, everything that can be written into
  std::ostream. The tags and attributes are declared by special helper functions.
      - Oboltus
*/

#if !defined(XMLW_xmlwriter_h)
#define XMLW_xmlwriter_h

namespace xmlw {

/// STL like ostream that outputs data in XML format
class XmlStream {
public:

  // XML version constants
  enum {versionMajor = 1, versionMinor = 0};

  // Internal helper class
  /// Maintains the current state of the XmlStream
  struct Controller {
    typedef enum {whatProlog, whatTag, whatTagEnd, whatAttribute, whatCharData, whatTagEndAll} what_type;

    what_type what;
    std::string str;

    inline Controller(const Controller& c) : what(c.what), str(c.str) {}
    inline Controller(const what_type _what) : what(_what){}
    
    // use template constructor because string field <str> may be initialized 
    // from different sources: char*, std::string etc
    template<class t>
    inline Controller(const what_type _what, const t& _str) : what(_what), str(_str) {}
  };

  // XmlStream refers std::ostream object to perform actual output operations
  inline XmlStream(std::ostream& _s) : s(_s), state(stateNone), inline_text(false), prologWritten(false) {}

  // Before destroying check whether all the open tags are closed
  ~XmlStream() {
    if (stateTagName == state) {
      s << "/>";
      state = stateNone;
    }
    // Commented out the auto tag close on destroy - added function endalltags() instead - kim
//    while (tags.size())
//      endTag(tags.top());
  }

  // default behaviour - delegate object output to std::stream
  template<class t>
  XmlStream& operator<<(const t& value) {
    if (stateTagName == state)
      tagName << value;
    s << value;
    return *this;
  }

  inline void indent(int offset = 0) const
  {
    static char tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
    s << '\n' << tabs+sizeof(tabs) - tags.size()-offset;
  }

  // this is the main working horse
  // and it's long a little
  XmlStream& operator<<(const Controller& controller) {

    switch (controller.what) {
    case Controller::whatProlog:
      if (!prologWritten && stateNone == state) {
        s << "<?xml version=\"" << versionMajor << '.' << versionMinor << "\"?>";
        prologWritten = true;
      }
      break; // Controller::whatProlog

    case Controller::whatTag:
      closeTagStart();
      indent(1);
      s << '<';
      if (controller.str.empty()) {
        clearTagName();
        state = stateTagName;
      }
      else {
        s << controller.str;
        tags.push(controller.str);
        state = stateTag;
      }
      break; // Controller::whatTag

    case Controller::whatTagEnd:
      endTag(controller.str);
      break; // Controller::whatTagEnd

    case Controller::whatTagEndAll:
      endAllTags();
      break; // Controller::whatTagEndAll

    case Controller::whatAttribute:
      switch (state) {
      case stateTagName:
        tags.push(tagName.str());
        break;

      case stateAttribute:
        s << '\"';
      }

      if (stateNone != state) {
        s << ' ' << controller.str << "=\"";
        state = stateAttribute;
      }
      // else throw some error - unexpected attribute (out of any tag)

      break; // Controller::whatAttribute

    case Controller::whatCharData:
      closeTagStart();
      inline_text = true;
      state = stateNone;
      break; // Controller::whatCharData
    }

    return *this;
  }

private:
  // state of the stream 
  typedef enum {stateNone, stateTag, stateAttribute, stateTagName} state_type;

  // tag name stack
  typedef std::stack<std::string> tag_stack_type;

  tag_stack_type tags;
  state_type state;
  std::ostream& s;
  bool inline_text;
  bool prologWritten;
  std::ostringstream tagName;

  // I don't know any way easier (legal) to clear std::stringstream...
  inline void clearTagName() {
    const std::string empty_str;
    tagName.rdbuf()->str(empty_str);
  }

  // Close current tag
  void closeTagStart(bool self_closed = false) {
    if (stateTagName == state)
      tags.push(tagName.str());

    // note: absence of 'break's is not an error
    switch (state) {
    case stateAttribute:
      s << '\"';

    case stateTagName:
    case stateTag:
      if (self_closed)
        s << " /";
      s << '>';
    }
  }

  // Close tag (may be with closing all of its children)
  void endTag(const std::string& tag) {
    bool brk = false;

    while (tags.size() > 0 && !brk) {
      if (stateNone == state)
      {
        if (!inline_text) indent();
        s << "</" << tags.top() << '>';
      }
      else {
        closeTagStart(true);
        state = stateNone;
      }
      brk = tag.empty() || tag == tags.top();
      tags.pop();
      inline_text = false;
    }
  }

  // Close all remaining tags
  void endAllTags() {
    while (tags.size())
      endTag(tags.top());
  }

};  // class XmlStream

// Helper functions, they may be simply overwritten
// E.g. you may use std::string instead of const char*

inline const XmlStream::Controller prolog() {
  return XmlStream::Controller(XmlStream::Controller::whatProlog);
}

inline const XmlStream::Controller tag() {
  return XmlStream::Controller(XmlStream::Controller::whatTag);
}

inline const XmlStream::Controller tag(const char* const tag_name) {
  return XmlStream::Controller(XmlStream::Controller::whatTag, tag_name);
}

inline const XmlStream::Controller endtag() {
  return XmlStream::Controller(XmlStream::Controller::whatTagEnd);
}

inline const XmlStream::Controller endtag(const char* const tag_name) {
  return XmlStream::Controller(XmlStream::Controller::whatTagEnd, tag_name);
}
// Added this function since we do not write to disk - kim
inline const XmlStream::Controller endalltags() {
  return XmlStream::Controller(XmlStream::Controller::whatTagEndAll);
}

inline const XmlStream::Controller attr(const char* const attr_name) {
  return XmlStream::Controller(XmlStream::Controller::whatAttribute, attr_name);
}

inline const XmlStream::Controller chardata() {
  return XmlStream::Controller(XmlStream::Controller::whatCharData);
}

} // namespace xmlw

#endif
